package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.VO.Catelog2Vo;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private RedissonClient redisson;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;


    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        // 1 查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        // 2 封装成父子的树形解构
        // 2.1 找到一级分类
        return entities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .peek(menu -> menu.setChildren(getChildren(menu, entities)))
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort()))) // 排序
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        // TODO: 2021/8/26 1 检查当前菜单是否被别的地方应用

        // 逻辑删除
        baseMapper.deleteBatchIds(asList);
    }


    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        findParentPath(catelogId, paths);

        Collections.reverse(paths);

        return paths.toArray(new Long[0]);
    }

    /**
     * 级联更新关联所有数据
     */
//    @CacheEvict(value = "category", key = "'getLevel1Categorys'") // 缓存失效，对category 分区下的指定key 有用
//    @Caching(evict = { // 执行多个cache 语句
//            @CacheEvict(value = "category", key = "'getLevel1Categorys'"),
//            @CacheEvict(value = "category", key = "'getCatelogJson'")
//    })
    @CacheEvict(value = "category", allEntries = true) // allEntries 属性表示是否删除当前分区下的所有key
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);

        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    /**
     * 从数据库中查询一级分类
     */
    // value:为给缓存添加一个命名分区，在cache有这个分区的概念，方便进行管理
    // key:给缓存中保存的数据命名，里面是一个SpEl表达式
    @Cacheable(value = {"category"}, key = "#root.methodName") // 对返回结果添加缓存功能，如果缓存里面有就不执行方法，没有就执行并将返回的结果加入到缓存
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys 从数据库中查询。。。");
        long l = System.currentTimeMillis();
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

    @Cacheable(value = "category", key = "#root.methodName", sync = true)
    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        System.out.println("查询了数据库。。。");

            /*
            1 将多次索引改为查询一次
            */
        List<CategoryEntity> selectList = baseMapper.selectList(new QueryWrapper<>(null));

        // 1 查询1级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);

        // 处理返回的1级vo
        Map<String, List<Catelog2Vo>> catelog1level = null;
        if (level1Categorys != null) {
            // 处理返回的2级vo
            catelog1level = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                List<Catelog2Vo> level2List = null;

                List<CategoryEntity> level2Entity = getParent_cid(selectList, v.getCatId());
                if (level2Entity != null) {
                    // 处理返回的3级vo
                    level2List = level2Entity.stream().map(level2 -> {
                        List<CategoryEntity> level3Entity = getParent_cid(selectList, level2.getCatId());
                        List<Catelog2Vo.Category3Vo> leve3List = null;
                        if (level3Entity != null) {
                            leve3List = level3Entity.stream().map(level3 -> {
                                return new Catelog2Vo.Category3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
                            }).collect(Collectors.toList());
                        }

                        return new Catelog2Vo(v.getCatId().toString(), leve3List, level2.getCatId().toString(), level2.getName());
                    }).collect(Collectors.toList());
                }

                return level2List;
            }));
        }

        return catelog1level;
    }

//    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson2() {
        /*
            缓存穿透，大量查询一个不存在的缓存：给缓存添加空值
            缓存雪崩，大量缓存同时失效：保存时在失效时间上再加一个随机时间
            缓存击穿，一个缓存失效的同时，大量的访问该资源：加锁
         */

        // 加入redis缓存
        // 1 从缓存中获取
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");

        if (StringUtils.isEmpty(catelogJson)) {
            // 为空，查数据库，加入缓存
            Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDbWithRedisLock();

            System.out.println("缓存 不 命中。。。");

            return catelogJsonFromDb;
        }

        System.out.println("缓存命中。。。");

        // 不为空，返回
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return result;
    }

    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedissonLock() {
        // 获取锁（可重入锁，一般都是用这个锁）
        RLock lock = redisson.getLock("CatelogJson-lock");

        lock.lock();

        Map<String, List<Catelog2Vo>> dataFromDb = null;
        // 获取成功，查询数据库，并删除锁
        try {
            dataFromDb = getDataFromDb();
        } finally {
            lock.unlock();
        }
        return dataFromDb;

    }

    /**
     * 从数据库查询并封装数据
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithRedisLock() {

        // 以uuid作为锁的value
        String uuid = UUID.randomUUID().toString();
        // 获取锁，获取锁和，设置过期操作为原子操作
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3000, TimeUnit.SECONDS);

        if (lock) {
            System.out.println("获取分布式锁成功！！！");
            Map<String, List<Catelog2Vo>> dataFromDb = null;
            // 获取成功，查询数据库，并删除锁
            try {
                dataFromDb = getDataFromDb();
            } finally {
                /*
                    删除锁（步骤，为防止程序执行时间过长，获取lock的value的时候数据村中途中过期被删除，然后下一个线程占有了锁但是当前线程获取到的uuid还是原来的uuid，一对比成功就会删除掉下一个线程的锁）
                    也需要是一个原子操作，使用redisTemplate.execute可以执行lua脚本，lua脚本会根据key以及value来删除lock
                */
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            System.out.println("获取分布式锁失败~~~");
            // 获取失败，自旋方式，继续获取
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            return getCatelogJsonFromDbWithRedisLock();
        }
    }

    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        if (!StringUtils.isEmpty(catelogJson)) {
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }

        System.out.println("查询了数据库。。。");

            /*
            1 将多次索引改为查询一次
            */
        List<CategoryEntity> selectList = baseMapper.selectList(new QueryWrapper<>(null));

        // 1 查询1级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);

        // 处理返回的1级vo
        Map<String, List<Catelog2Vo>> catelog1level = null;
        if (level1Categorys != null) {
            // 处理返回的2级vo
            catelog1level = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                List<Catelog2Vo> level2List = null;

                List<CategoryEntity> level2Entity = getParent_cid(selectList, v.getCatId());
                if (level2Entity != null) {
                    // 处理返回的3级vo
                    level2List = level2Entity.stream().map(level2 -> {
                        List<CategoryEntity> level3Entity = getParent_cid(selectList, level2.getCatId());
                        List<Catelog2Vo.Category3Vo> leve3List = null;
                        if (level3Entity != null) {
                            leve3List = level3Entity.stream().map(level3 -> {
                                return new Catelog2Vo.Category3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
                            }).collect(Collectors.toList());
                        }

                        return new Catelog2Vo(v.getCatId().toString(), leve3List, level2.getCatId().toString(), level2.getName());
                    }).collect(Collectors.toList());
                }

                return level2List;
            }));
        }

        String s = JSON.toJSONString(catelog1level);
        // 加入缓存，并设置过期时间
        redisTemplate.opsForValue().set("catelogJson", s, 1L, TimeUnit.DAYS);

        return catelog1level;
    }

    /**
     * 从数据库查询并封装数据
     *
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDbWithLocalLock() {

//        Map<String, List<Catelog2Vo>> catelogJson = (Map<String, List<Catelog2Vo>>) cash.get("catelog1level");
//        if (catelogJson == null) {
//            查询数据库
//            放入缓存
//            cash.put("catelogJson", catelog1level);
//
//        }
//        return catelogJson;

        /*
            本地锁：
            synchronized (this)，所有的线程使用的实例都是一个，本对象在spring容器中默认是单例的
            synchronized 或JUC（lock） 都是本地锁，想要锁住所有的分布式服务，必须使用分布式锁
         */
        /*
            分布式锁：

         */

        synchronized (this) {
            // 线程获取到锁之后再查一遍缓存
            return getDataFromDb();
        }
    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
        List<CategoryEntity> collect = selectList.stream().filter(item -> item.getParentCid() == parent_cid).collect(Collectors.toList());
        return collect;
    }

    private void findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
    }


    /**
     * 递归查找当前菜单的子菜单
     */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
        return all.stream()
                .filter(child -> child.getParentCid().equals(root.getCatId()))
                .peek(menu -> menu.setChildren(getChildren(menu, all))) // 递归找子菜单
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort()))) // 排序
                .collect(Collectors.toList());
    }
}